home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-03 / huffman2.zip / HUFFMAN2.BAS < prev    next >
BASIC Source File  |  1992-06-01  |  14KB  |  486 lines

  1. 'Huffman encoder
  2. 'by Rich Geldreich May 29th, 1992
  3. 'This program is in the public domain.
  4. DEFINT A-Z
  5. DECLARE SUB InitTree ()
  6. DECLARE SUB MakeSortTable ()
  7. DECLARE SUB CombineTree ()
  8. DECLARE SUB CleanUpTree ()
  9. DECLARE SUB WriteTree ()
  10.  
  11. DECLARE SUB SortDistribution2 ()
  12. DECLARE SUB SortDistribution ()
  13. DECLARE SUB GetDistribution ()
  14. DECLARE SUB RecurseTree (Node)
  15.  
  16. DECLARE SUB FillBuffer ()
  17.  
  18.  
  19. CONST True = -1, False = 0
  20. CONST Null = -2
  21. CONST BufferLength = 10000
  22.  
  23. CLEAR , , 10000
  24.  
  25. DIM SHARED Father(512) AS LONG, LeftSon(512), RightSon(512)
  26. DIM SHARED Index(512), RealIndex, Used(255) AS LONG
  27. DIM SHARED Pointer(255), HighestEntry
  28. DIM SHARED Code(255, 40), CodeLength(255)
  29. DIM SHARED CurrentLength, CurrentCode(40)
  30.  
  31. DIM SHARED Buffer$, Address, EndAddress, Bits(8), CurrentByte, CurrentBit
  32. DIM SHARED BufferSeg
  33.  
  34.  
  35. LOCATE , , 1
  36.  
  37.  
  38. Bits:
  39.     DATA 1,2,4,8,16,32,64,128,256
  40.  
  41. 'read the bit masks
  42. RESTORE Bits
  43. FOR A = 0 TO 8: READ Bits(A): NEXT
  44.  
  45. 'initilize the tree
  46. InitTree
  47.  
  48. 'initlize the input buffer
  49. Buffer$ = STRING$(BufferLength, 0)
  50. EndAddress = 1: Address = 0
  51.  
  52. PRINT "Getting Distribution:";
  53. 'open input file
  54. OPEN COMMAND$ FOR BINARY AS #1
  55. 'check to see if it exists
  56. IF LOF(1) = 0 THEN
  57.     CLOSE #1
  58.     KILL COMMAND$
  59.     PRINT
  60.     PRINT COMMAND$; " not found"
  61.     END
  62. END IF
  63. 'read the input file and gather the distribution of each character
  64. GetDistribution
  65. 'make a sorting table
  66. MakeSortTable
  67. 'sort the table with the Shell Metzer sort
  68. SortDistribution
  69. 'combine the tree until there is only one node at the "top"
  70. CombineTree
  71. 'work down the tree finding codes which represent each character
  72. TopOfTree = Pointer(0)
  73. CurrentLength = 0
  74. RecurseTree TopOfTree
  75. 'for debugging: prints the code for each character
  76. 'FOR A = 0 TO 255
  77. '    IF Used(A) > 256 THEN
  78. '        PRINT A;
  79. '        FOR B = 0 TO CodeLength(A)
  80. '            PRINT Code(A, B);
  81. '        NEXT
  82. '        PRINT
  83. '    END IF
  84. 'NEXT
  85. 'END
  86. '"cleans" the tree up so it can be sent as small as possible
  87. CleanUpTree
  88.  
  89. CurrentByte = 0: CurrentBit = 0
  90. RealIndex = RealIndex - 1
  91. 'open output file
  92. OPEN "output.huf" FOR BINARY AS #2
  93. 'kill file if it already exists
  94. IF LOF(2) <> 0 THEN
  95.     CLOSE #2
  96.     KILL "output.huf"
  97.     OPEN "output.huf" FOR BINARY AS #2
  98. END IF
  99.  
  100. 'put the header
  101. A& = LOF(1)
  102. PUT #2, , A&            'number of bytes in original file
  103. PUT #2, , RealIndex     'number of nodes in tree
  104. Top = Index(TopOfTree)
  105. PUT #2, , Top           'top of tree
  106.  
  107. WriteTree               'writes the tree to the output file
  108.  
  109. 'compresses the input file
  110. PRINT : PRINT "Encoding...": PRINT : PRINT
  111. Ypos = CSRLIN - 2
  112.  
  113. SEEK #1, 1
  114. EndAddress = 1: Address = 0
  115. 'initilize the output buffer
  116. A$ = STRING$(5000, 0)
  117. A& = SADD(A$)
  118. A& = A& - 65536 * (A& < 0)
  119. OBufferSeg = VARSEG(A$) + (A& \ 16)
  120. OAddress = (A& MOD 16)
  121. OEndAddress = OAddress + 5000
  122. Ostart = OAddress
  123. 'start compressing
  124. FOR A& = 1 TO LOF(1)
  125.    
  126.     'get a byte from the input file
  127.     Address = Address + 1
  128.     'if Address=EndBuffer then it's time to fill the input buffer
  129.     IF Address = EndAddress THEN FillBuffer
  130.     B = PEEK(Address)
  131.     'send out all of the bits that represent the input character
  132.     FOR C = 0 TO CodeLength(B)
  133.         IF Code(B, C) THEN
  134.             CurrentByte = CurrentByte * 2 OR 1      'send "1"
  135.         ELSE
  136.             CurrentByte = CurrentByte * 2           'send "0"
  137.         END IF
  138.         CurrentBit = CurrentBit + 1
  139.         'if CurrentBit=8 then we have a complete byte
  140.         IF CurrentBit = 8 THEN
  141.             DEF SEG = OBufferSeg
  142.             POKE OAddress, CurrentByte
  143.             OAddress = OAddress + 1
  144.             'if Oaddress=Oendaddress then it's time to flush the
  145.             'output buffer
  146.             IF OAddress = OEndAddress THEN
  147.                 PUT #2, , A$
  148.                 B& = SADD(A$)
  149.                 B& = B& - 65536 * (B& < 0)
  150.                 OBufferSeg = VARSEG(A$) + (B& \ 16)
  151.                 OAddress = (B& MOD 16)
  152.                 OEndAddress = OAddress + 5000
  153.                 Ostart = OAddress
  154.             END IF
  155.             CurrentByte = 0: CurrentBit = 0
  156.             DEF SEG = BufferSeg
  157.         END IF
  158.     NEXT
  159.     'see if it's time to update screen
  160.     PrintCount = PrintCount + 1
  161.     IF PrintCount = 1024 THEN
  162.         PrintCount = 0
  163.         LOCATE Ypos, 1
  164.         PRINT "Bytes In:"; A&; (A& * 100&) \ LOF(1); "%  "
  165.         B& = LOF(2) + OAddress - Ostart
  166.         PRINT "Bytes Out:"; B&; "   "
  167.         PRINT "Compression:"; 100 - (B& * 100&) \ A&; "% ";
  168.     END IF
  169. NEXT
  170. 'put whatever is left of the byte buffer into the output buffer
  171. DO UNTIL CurrentBit = 8
  172.     CurrentByte = CurrentByte * 2
  173.     CurrentBit = CurrentBit + 1
  174. LOOP
  175.  
  176. DEF SEG = OBufferSeg
  177. POKE OAddress, CurrentByte
  178. A$ = LEFT$(A$, OAddress + 1 - Ostart)
  179. PUT #2, , A$
  180. 'report compression
  181. LOCATE Ypos, 1
  182. PRINT "Bytes In:"; LOF(1); SPACE$(16)
  183. PRINT "Bytes Out:"; LOF(2); SPACE$(16)
  184. PRINT "Overall Compression:"; 100 - (LOF(2) * 100&) \ LOF(1); "%"; SPACE$(16);
  185. CLOSE
  186.  
  187. END
  188.  
  189. '"Cleans" up the tree so it can be sent.
  190. SUB CleanUpTree
  191.     RealIndex = 0
  192.     FOR A = 0 TO 512
  193.         B& = Father(A)
  194.         IF B& <> Null THEN
  195.             IF B& < 256 THEN
  196.                 IF Used(B&) > 256 THEN
  197.                     Index(A) = RealIndex
  198.                     RealIndex = RealIndex + 1
  199.                 END IF
  200.             ELSEIF B& > 256 THEN
  201.                 Index(A) = RealIndex
  202.                 RealIndex = RealIndex + 1
  203.             END IF
  204.         END IF
  205.     NEXT
  206.  
  207.     FOR A = 0 TO 512
  208.         B& = Father(A)
  209.         IF B& <> Null THEN
  210.             IF B& < 256 THEN
  211.                 IF Used(B&) > 256 THEN
  212.                     IF LeftSon(A) <> Null THEN
  213.                         LeftSon(A) = Index(LeftSon(A))
  214.                     END IF
  215.                     IF RightSon(A) <> Null THEN
  216.                         RightSon(A) = Index(RightSon(A))
  217.                     END IF
  218.                 END IF
  219.             ELSEIF B& > 256 THEN
  220.                 IF LeftSon(A) <> Null THEN
  221.                     LeftSon(A) = Index(LeftSon(A))
  222.                 END IF
  223.                 IF RightSon(A) <> Null THEN
  224.                     RightSon(A) = Index(RightSon(A))
  225.                 END IF
  226.             END IF
  227.         END IF
  228.     NEXT
  229. END SUB
  230.  
  231. 'Combines the tree until there is only one node at the top.
  232. SUB CombineTree
  233.     
  234.     Parents = HighestEntry + 1
  235.     DO UNTIL Parents = 1
  236.         'sort the current distribution
  237.         SortDistribution2
  238.         'find the lowest 2 entries
  239.         Lowest = Pointer(HighestEntry)
  240.         NextLowest = Pointer(HighestEntry - 1)
  241.         'find new frequency
  242.         NewFrequency& = Father(Lowest) + Father(NextLowest) - 256
  243.         'combine the two nodes
  244.         IF RightSon(Lowest) = Null AND RightSon(NextLowest) = Null THEN
  245.             Father(NextLowest) = NewFrequency&
  246.             RightSon(NextLowest) = LeftSon(Lowest)
  247.             Father(Lowest) = Null
  248.             Parents = Parents - 1
  249.             HighestEntry = HighestEntry - 1
  250.         ELSEIF RightSon(Lowest) = Null AND RightSon(NextLowest) <> Null THEN
  251.             Father(Lowest) = NewFrequency&
  252.             RightSon(Lowest) = NextLowest
  253.             Pointer(HighestEntry - 1) = Pointer(HighestEntry)
  254.             Parents = Parents - 1
  255.             HighestEntry = HighestEntry - 1
  256.         ELSEIF RightSon(Lowest) <> Null AND RightSon(NextLowest) = Null THEN
  257.             Father(NextLowest) = NewFrequency&
  258.             RightSon(NextLowest) = Lowest
  259.             Parents = Parents - 1
  260.             HighestEntry = HighestEntry - 1
  261.         ELSEIF RightSon(Lowest) <> Null AND RightSon(NextLowest) <> Null THEN
  262.             'search for new node
  263.             FOR A = 512 TO 0 STEP -1
  264.                 IF Father(A) = Null THEN EXIT FOR
  265.             NEXT
  266.             Father(A) = NewFrequency&
  267.             LeftSon(A) = Lowest
  268.             RightSon(A) = NextLowest
  269.       
  270.             HighestEntry = HighestEntry - 1
  271.             Pointer(HighestEntry) = A
  272.             Parents = Parents - 1
  273.         END IF
  274.     'loop until there is only one node at the top
  275.     LOOP
  276.  
  277. END SUB
  278.  
  279. 'Fills the input buffer.
  280. SUB FillBuffer
  281.     GET #1, , Buffer$
  282.  
  283.     A& = SADD(Buffer$)
  284.     A& = A& - 65536 * (A& < 0)
  285.     BufferSeg = VARSEG(Buffer$) + (A& \ 16)
  286.     Address = (A& MOD 16)
  287.     EndAddress = Address + BufferLength
  288.     DEF SEG = BufferSeg
  289.  
  290. END SUB
  291.  
  292. 'Scans the input file for it's distribution.
  293. SUB GetDistribution
  294.        
  295.     FOR A& = 1 TO LOF(1)
  296.         Address = Address + 1
  297.         IF Address = EndAddress THEN
  298.             FillBuffer
  299.             PRINT ".";
  300.         END IF
  301.         B = PEEK(Address) * 2
  302.         Father(B) = Father(B) + 1
  303.     NEXT
  304.     B = 0
  305.     FOR A = 0 TO 510 STEP 2
  306.         Used(B) = Father(A): B = B + 1
  307.     NEXT
  308. END SUB
  309.  
  310. 'Initilizes the tree.
  311. SUB InitTree
  312.     B = 0
  313.     FOR A = 0 TO 510 STEP 2
  314.   
  315.         Father(A) = 256
  316.         LeftSon(A) = A + 1
  317.         RightSon(A) = Null
  318.   
  319.         Father(A + 1) = B
  320.         LeftSon(A + 1) = Null
  321.         RightSon(A + 1) = Null
  322.   
  323.         B = B + 1
  324.     NEXT
  325. END SUB
  326.  
  327. 'Makes a sorting table.
  328. SUB MakeSortTable
  329.     HighestEntry = 0
  330.     FOR A = 0 TO 510 STEP 2
  331.         IF Father(A) > 256 THEN
  332.             Pointer(HighestEntry) = A
  333.             HighestEntry = HighestEntry + 1
  334.         END IF
  335.     NEXT
  336.     HighestEntry = HighestEntry - 1
  337. END SUB
  338.  
  339. 'Recursize procedure to go down the tree and build up codes
  340. 'that represent each character.
  341. SUB RecurseTree (Node)
  342.     'are we at a character?
  343.     IF Father(Node) < 256 THEN
  344.         'yup! we CurrentCode() has this character's bit sequence
  345.         Char = Father(Node)
  346.         FOR A = 0 TO CurrentLength - 1
  347.             Code(Char, A) = CurrentCode(A)
  348.         NEXT
  349.         CodeLength(Char) = CurrentLength - 1
  350.     END IF
  351.     'go to the left if there's something there
  352.     IF LeftSon(Node) <> Null THEN
  353.         CurrentCode(CurrentLength) = 1      'add "1" to the current code
  354.         CurrentLength = CurrentLength + 1
  355.         RecurseTree LeftSon(Node)           'go down
  356.         CurrentLength = CurrentLength - 1   'take "1" from the current code
  357.     END IF
  358.     'go to the right if there's something there
  359.     IF RightSon(Node) <> Null THEN
  360.         CurrentCode(CurrentLength) = 0      'add "0" to the current code
  361.         CurrentLength = CurrentLength + 1
  362.         RecurseTree RightSon(Node)          'got down
  363.         CurrentLength = CurrentLength - 1   'take "0" from the current code
  364.     END IF
  365. END SUB
  366.  
  367. 'A REAL Shell sort follows. It is much faster than the well-known one.
  368. 'Sorts the nodes according to the sorting table.
  369. SUB SortDistribution
  370.     Offset = HighestEntry \ 2
  371.     DO
  372.         FOR I = 0 TO HighestEntry - Offset
  373.             IF Father(Pointer(I)) < Father(Pointer(I + Offset)) THEN
  374.                 SWAP Pointer(I), Pointer(I + Offset)
  375.                 CompareLow = I - Offset
  376.                 CompareHigh = I
  377.                 DO WHILE CompareLow >= 0
  378.                     IF Father(Pointer(CompareLow)) < Father(Pointer(CompareHigh)) THEN
  379.                         SWAP Pointer(CompareLow), Pointer(CompareHigh)
  380.                         CompareHigh = CompareLow
  381.                         CompareLow = CompareLow - Offset
  382.                     ELSE
  383.                         EXIT DO
  384.                     END IF
  385.                 LOOP
  386.             END IF
  387.         NEXT
  388.         Offset = Offset \ 2
  389.     LOOP WHILE Offset > 0
  390.     
  391.  
  392. END SUB
  393.  
  394. 'A simple bubble sort... used while combining the tree.
  395. SUB SortDistribution2
  396.     
  397.     DO
  398.         SwapFlag = False
  399.         FOR A = HighestEntry - 1 TO 0 STEP -1
  400.             IF Father(Pointer(A + 1)) > Father(Pointer(A)) THEN
  401.                 SWAP Pointer(A + 1), Pointer(A)
  402.                 SwapFlag = True
  403.             END IF
  404.         NEXT
  405.     LOOP WHILE SwapFlag
  406.     
  407. END SUB
  408.  
  409. 'Writes the tree to disk.
  410. SUB WriteTree
  411.     
  412.  
  413.     FOR A = 0 TO 512
  414.         B& = Father(A)
  415.         IF B& <> Null THEN
  416.             IF B& < 256 THEN
  417.                 IF Used(B&) > 256 THEN
  418.                     GOSUB SendOne
  419.                     FOR C = 0 TO 7
  420.                         IF (B& AND Bits(C)) > 0 THEN
  421.                             GOSUB SendOne
  422.                         ELSE
  423.                             GOSUB SendZero
  424.                         END IF
  425.                     NEXT
  426.                 END IF
  427.             ELSEIF B& > 256 THEN
  428.                 GOSUB SendZero
  429.                 IF LeftSon(A) <> Null THEN
  430.                     GOSUB SendOne
  431.                     Son = LeftSon(A)
  432.                
  433.                     FOR C = 0 TO 8
  434.                         IF (Son AND Bits(C)) > 0 THEN
  435.                             GOSUB SendOne
  436.                         ELSE
  437.                             GOSUB SendZero
  438.                         END IF
  439.                     NEXT
  440.                 ELSE
  441.                     GOSUB SendZero
  442.                 END IF
  443.                 IF RightSon(A) <> Null THEN
  444.                     GOSUB SendOne
  445.                     Son = RightSon(A)
  446.                    
  447.                     FOR C = 0 TO 8
  448.                         IF (Son AND Bits(C)) > 0 THEN
  449.                             GOSUB SendOne
  450.                         ELSE
  451.                             GOSUB SendZero
  452.                         END IF
  453.                     NEXT
  454.                 ELSE
  455.                     GOSUB SendZero
  456.                 END IF
  457.             END IF
  458.         END IF
  459.     NEXT
  460.  
  461.     EXIT SUB
  462.  
  463. SendZero:
  464.     CurrentByte = CurrentByte * 2
  465.     CurrentBit = CurrentBit + 1
  466.     IF CurrentBit = 8 THEN
  467.         A$ = CHR$(CurrentByte)
  468.         PUT #2, , A$
  469.         CurrentByte = 0: CurrentBit = 0
  470.     END IF
  471. RETURN
  472.  
  473. SendOne:
  474.    
  475.     CurrentByte = CurrentByte * 2 OR 1
  476.     CurrentBit = CurrentBit + 1
  477.     IF CurrentBit = 8 THEN
  478.         A$ = CHR$(CurrentByte)
  479.         PUT #2, , A$
  480.         CurrentByte = 0: CurrentBit = 0
  481.     END IF
  482. RETURN
  483.  
  484. END SUB
  485.  
  486.